package graylog

import (
	"bytes"
	"compress/zlib"
	"crypto/rand"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"log"
	"math"
	"net"
	"os"
	"strings"
	"time"

	"github.com/astaxie/beego"
)

const (
	defaultGraylogHost     = "127.0.0.1"
	defaultGraylogPort     = "12201"
	defaultConnection      = "wan"
	defaultMaxChunkSizeWan = 1420
	defaultMaxChunkSizeLan = 8154

	levelINFO  = 6
	levelERROR = 3
)

type grayConfig struct {
	Application     string
	GraylogHost     string
	GraylogPort     string
	Connection      string
	MaxChunkSizeWan int
	MaxChunkSizeLan int
	HostName        string
}

type Gelf struct {
	grayConfig
}

// M 为了简化代码
type M map[string]interface{}

var gLog, gAPI *Gelf

// Init 初始化greylog
func Init() {
	gLog = initGraylog(grayConfig{
		Application: "chain-app",
		GraylogHost: beego.AppConfig.String("GraylogHost"),
		GraylogPort: beego.AppConfig.String("GraylogPort"),
		Connection:  "lan",
	})
	if beego.AppConfig.String("GraylogApiHost") != "" {
		gAPI = initGraylog(grayConfig{
			Application: "chain-app-api",
			GraylogHost: beego.AppConfig.String("GraylogApiHost"),
			GraylogPort: beego.AppConfig.String("GraylogApiPort"),
			Connection:  "lan",
		})
	}
}

//// Info 正常消息
//func Info(message string, data M) {
//	gLog.Log(levelINFO, message, data)
//}

// InfoAPI 接口消息
func InfoAPI(message string, data M) {
	if gAPI != nil {
		gAPI.Log(levelINFO, message, data)
	} else {
		gLog.Log(levelINFO, message, data)
	}
}

// Error 错误消息
func Error(message string, data M) {
	gLog.Log(levelERROR, message, data)
}

// CheckErr 检验错误
func CheckErr(err error, message string, data M) {
	if err != nil {
		data["err"] = err
		Error("err_"+message, data)
	}
}

func initGraylog(config grayConfig) (g *Gelf) {
	if config.GraylogPort == "" {
		config.GraylogPort = defaultGraylogPort
	}
	if config.GraylogHost == "" {
		config.GraylogHost = defaultGraylogHost
	}
	if config.Connection == "" {
		config.Connection = defaultConnection
	}
	if config.MaxChunkSizeWan == 0 {
		config.MaxChunkSizeWan = defaultMaxChunkSizeWan
	}
	if config.MaxChunkSizeLan == 0 {
		config.MaxChunkSizeLan = defaultMaxChunkSizeLan
	}
	if config.HostName == "" {
		config.HostName, _ = os.Hostname()
	}
	g = &Gelf{
		grayConfig: config,
	}
	return g
}

// Log 消息输出
func (g *Gelf) Log(level uint, message string, data M) {
	obj := M{
		"version":       "1.0",
		"host":          g.Application,
		"_server":       g.HostName,
		"short_message": message,
		"level":         level,
	}
	for k, v := range data {
		obj[fmt.Sprint("_", k)] = v
	}
	msg, _ := json.Marshal(obj)
	compressed := g.compress(msg)
	chunksize := g.MaxChunkSizeWan
	length := compressed.Len()
	if length > chunksize {
		chunkCountInt := length / chunksize
		chunkCount := math.Ceil(float64(chunkCountInt))
		chunkCountInt = int(chunkCount) + 1
		id := make([]byte, 8)
		rand.Read(id)
		for i, index := 0, 0; i < length; i, index = i+chunksize, index+1 {
			packet := g.createChunkedMessage(index, chunkCountInt, id, &compressed)
			g.send(packet.Bytes())
		}
	} else {
		g.send(compressed.Bytes())
	}
	if beego.BConfig.RunMode != "prod" && beego.BConfig.RunMode != "app" {
		if strings.Contains(message, "异常") {
			fmt.Println("\033[31m["+time.Now().String(), "]\033[0m")
			fmt.Println("Message:", message)
			fmt.Println("\033[31mData", data["data"], "\033[0m", "\n ")
		} else {
			fmt.Println("\033[32m["+time.Now().String(), "]\033[0m")
			if data["errcode"] == "0" {
				fmt.Println("Message:", message)
				fmt.Println("\033[33mData:", data["data"], "\nResData:", data["resData"], "\033[0m", "\n ")
			} else {
				fmt.Println("Message:", message)
				fmt.Println("\033[31mData:", data["data"], "\nResData:", data["resData"], "\033[0m", "\n ")
			}
		}
	}
}

func (g *Gelf) createChunkedMessage(index int, chunkCountInt int, id []byte, compressed *bytes.Buffer) bytes.Buffer {
	var packet bytes.Buffer

	chunksize := g.getChunksize()
	packet.Write(g.intToBytes(30))
	packet.Write(g.intToBytes(15))
	packet.Write(id)
	packet.Write(g.intToBytes(index))
	packet.Write(g.intToBytes(chunkCountInt))
	packet.Write(compressed.Next(chunksize))

	return packet
}

func (g *Gelf) getChunksize() int {
	if g.Connection == "lan" {
		return g.MaxChunkSizeLan
	}
	return g.MaxChunkSizeWan
}

func (g *Gelf) intToBytes(i int) []byte {
	buf := new(bytes.Buffer)
	err := binary.Write(buf, binary.LittleEndian, int8(i))
	if err != nil {
		log.Printf("Uh oh! %s", err)
	}
	return buf.Bytes()
}

func (g *Gelf) compress(b []byte) bytes.Buffer {
	var buf bytes.Buffer
	comp := zlib.NewWriter(&buf)
	comp.Write(b)
	comp.Close()
	return buf
}

func (g *Gelf) send(b []byte) {
	var addr = fmt.Sprintf("%s:%s", g.GraylogHost, g.GraylogPort)
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		log.Printf("Uh oh! %s", err)
		return
	}
	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		log.Printf("Uh oh! %s", err)
		return
	}
	conn.Write(b)
}
